#include <iostream>
#include <cstring>
#include <cstdlib>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>

#include "shared_ptr.hpp"
#include "ListenThread.h"
#include "ConnQueue.h"

using namespace std;

typedef struct sockaddr sockaddr_t;

static void setnonblocking(int sock)
{
    int opts = fcntl(sock,F_GETFL);
    if (opts < 0)
    {
        cerr << "Failed to F_GETFL" << endl;;
        return;
    }

    opts = opts | O_NONBLOCK;
    if (fcntl(sock,F_SETFL,opts) < 0)
    {
        cerr << "Failed to F_SETFL" << endl;
        return;
    }
}

ListenThread::ListenThread(uint32 port)
    : _port(port)
{ }

struct sockaddr_in* ListenThread::GetSvrAddr()
{
    struct sockaddr_in* svrAddr = new sockaddr_in();
    memset(svrAddr, 0, sizeof(struct sockaddr_in));
    svrAddr->sin_family = AF_INET;
    svrAddr->sin_addr.s_addr = htonl(INADDR_ANY);
    svrAddr->sin_port = htons(_port);

    return svrAddr;
}

void ListenThread::Bind(int listenfd)
{
    boost::shared_ptr<struct sockaddr_in> svrAddr(GetSvrAddr());
    if (bind(listenfd, (struct sockaddr*)svrAddr.get(), sizeof(struct sockaddr_in)) < 0)
    {
        cerr << "Failed to bind port [" << _port << "]" << endl;
        exit(3);
    }
}

void ListenThread::Listen(int listenfd)
{
    if (listen(listenfd, 5) < 0)
    {
        cerr << "Failed to listen port [" << _port << "]" << endl;
        exit(4);
    }
}

void ListenThread::Accept(int listenfd)
{
    while (true)
    {
        boost::shared_ptr<sockaddr_t> clientAddr;

        uint32 addrLen = sizeof(sockaddr_t);
        int connfd = 0;
        if ((connfd = accept(listenfd, clientAddr.get(), &addrLen)) < 0)
        {
            cerr << "Failed to accept a client connection. errno = [" << errno << "]" << endl;

            continue;
        }

        ConnQueue::Instance()->Push(connfd);
    }
}

//void ListenThread::Accept(int listenfd)
//{
//    int epfd = epoll_create(20);
//    if (epfd < 0)
//    {
//        cerr << "Failed to create epoll obj" << endl;
//        exit(5);
//    }
//
//    const uint32 MAX_EVE_CNT = 10;
//    struct epoll_event events[MAX_EVE_CNT];
//    struct epoll_event ev;
//
//    // setnonblocking(listenfd);
//    ev.events = EPOLLIN | EPOLLET;
//    if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) < 0)
//    {
//        cerr << "Failed to epoll_ctl listenfd" << endl;
//        exit(6);
//    }
//
//    while (true)
//    {
//        int nfds = epoll_wait(epfd, events, MAX_EVE_CNT, -1);
//        for (int i = 0; i < nfds; i++)
//        {
//            if (events[i].data.fd != listenfd)
//            {
//                cout << "Push [" << events[i].data.fd << "] to queue" << endl;
//                ConnQueue::Instance()->Push(events[i].data.fd);
//                continue;
//            }
//
//            boost::shared_ptr<sockaddr_t> clientAddr(new sockaddr_t());
//            uint32 addrLen = sizeof(sockaddr_t);
//
//            int connfd = 0;
//            if ((connfd = accept(listenfd, clientAddr.get(), &addrLen)) < 0)
//            {
//                cerr << "Failed to accept a client connection. errno = [" << errno << "]" << endl;
//                continue;
//            }
//
//            if (epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev) < 0)
//            {
//                cerr << "Failed to epoll_ctl listenfd" << endl;
//                close(connfd);
//                continue;
//            }
//            cout << "Add [" << connfd << "] to epoll" << endl;
//        }
//    }
//}

void* ListenThread::Process()
{
    if (_port < 1024 && _port != 80)
    {
        cerr << "You should bind port > 1024 or 80" << endl;
        exit(1);
    }
    int listenfd = 0;
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        cerr << "Failed to create listen socket." << endl;
        exit(2);
    }
    cout << "listenfd = " << listenfd << endl;

    Bind(listenfd);
    Listen(listenfd);
    Accept(listenfd);

    return NULL;
}
